// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/exchange/base/symbology/product.h"

#include <gtest/gtest.h>

#include "coin2/base/log.h"
#include "coin2/exchange/base/symbology/futures_product.h"
#include "coin2/exchange/base/symbology/product_info.h"
#include "coin2/exchange/base/symbology/spot_product.h"
#include "coin2/exchange/huobi/symbology/product.h"
#include "coin2/exchange/huobi_futures/symbology/product.h"

// TODO(xguo, jshin): move this to HuobiProduct Test
// TEST(Product, constructor) {
//   using namespace coin2::exchange::base::symbology;
//   using Product = CurrencyPairProduct<Currency>;
//   ExchangeType exchange = ExchangeType::UNKNOWN_EXCHANGE_TYPE;
//   MarketType market_type = MarketType::UNKNOWN_MARKET_TYPE;

//   Currency btc("BTC", "btc", exchange);
//   Currency usdt("USDT", "usdt", exchange);
//   std::string product_str("BTC-USDT");
//   std::string native_product("btc/usdt");
//   std::string subscription_symbol("BTC-USDT-SUB");
//   std::string native_subscription_symbol("btc/usdt/sub");

//   Product product(
//       btc, usdt,
//       product_str, native_product,
//       subscription_symbol, native_subscription_symbol,
//       market_type, exchange);

//   EXPECT_EQ(product.base(), btc);
//   EXPECT_EQ(product.quote(), usdt);
//   EXPECT_EQ(product.absolute_norm(), product_str);
//   EXPECT_EQ(product.order_native_symbol(), native_product);
//   EXPECT_EQ(product.subscription_symbol(), subscription_symbol);
//   EXPECT_EQ(product.native_subscription_symbol(), native_subscription_symbol);
//   EXPECT_EQ(product.exchange(), exchange);
//   EXPECT_EQ(product.exchange_name(), "UNKNOWN_EXCHANGE_TYPE");
//   EXPECT_EQ(product.market(), market_type);
//   EXPECT_EQ(product.market_name(), "UNKNOWN_MARKET_TYPE");
// }

// TEST(SpotProduct, constructor) {
//   using namespace coin2::exchange::base::symbology;
//   using namespace coin2::exchange::huobi::symbology;
//   using SpotProduct = SpotProduct<Currency, HuobiProduct>;
//   ExchangeType exchange = ExchangeType::Huobi;
//   MarketType market_type = MarketType::Spot;

//   Currency btc("BTC", "btc", exchange);
//   Currency usdt("USDT", "usdt", exchange);
//   std::string product_str("BTC-USDT");
//   std::string native_product("btcusdt");
//   std::string native_subscription_symbol("btcusdt/sub");
//   SpotProduct product(btc, usdt, native_product, native_subscription_symbol, exchange);

//   EXPECT_EQ(product.base(), btc);
//   EXPECT_EQ(product.quote(), usdt);
//   EXPECT_EQ(product.absolute_norm(), product_str);
//   EXPECT_EQ(product.order_native_symbol(), native_product);
//   EXPECT_EQ(product.subscription_symbol(), product_str);
//   EXPECT_EQ(product.native_subscription_symbol(), native_subscription_symbol);
//   EXPECT_EQ(product.exchange(), exchange);
//   EXPECT_EQ(product.exchange_name(), "Huobi");
//   EXPECT_EQ(product.market(), market_type);
//   EXPECT_EQ(product.market_name(), "Spot");
// }

// TEST(FuturesProduct, constructor) {
//   using namespace coin2::exchange::base::symbology;
//   using namespace coin2::exchange::huobi_futures::symbology;
//   using FuturesProduct = FuturesProduct<Currency, HuobiFuturesProduct>;
//   ExchangeType exchange = ExchangeType::Huobi;

//   Currency btc("BTC", "btc", exchange);
//   Currency usd("USD", "usd", exchange);
//   std::string native_product("BTC191212");
//   std::string native_subscription_symbol("BTC_CW");
//   int64_t resolve_timestamp = 1573114188 * 1e9;  // 2019-11-07 08:09:48 UTC
//   int64_t expiry_timestamp = (1573114188 + 7 * 3600 * 24) * 1e9;
//   FuturesProduct product(
//       btc,
//       usd,
//       native_product,
//       native_subscription_symbol,
//       exchange,
//       ContractType::WEEK,
//       expiry_timestamp,
//       resolve_timestamp);
//   EXPECT_EQ(product.base(), btc);
//   EXPECT_EQ(product.quote(), usd);
//   EXPECT_EQ(product.market(), MarketType::Futures);
//   EXPECT_EQ(product.exchange(), ExchangeType::Huobi);
//   EXPECT_EQ(product.absolute_norm(), "BTC-USD.20191114");
// }

TEST(FixedTickPrice, fixed_tick_price) {
  using namespace coin2::exchange::base::symbology;

  double price;
  double prev_price;
  double next_price;

  auto tick_price = FixedTickPrice(0.3);
  EXPECT_EQ(tick_price.max_precision(), 1);
  price = 9.3;

  prev_price = tick_price.GetPrevPrice(price);
  next_price = tick_price.GetNextPrice(price);
  EXPECT_NEAR(9.0, prev_price, 1.e-10);
  EXPECT_NEAR(9.6, next_price, 1.e-10);

  prev_price = tick_price.GetNthPrevPrice(price, 2);
  next_price = tick_price.GetNthNextPrice(price, 2);
  EXPECT_NEAR(8.7, prev_price, 1.e-10);
  EXPECT_NEAR(9.9, next_price, 1.e-10);

  // price = tick_price.Ceil(9.33);
  // EXPECT_NEAR(price, 9.6, 1e-10);

  // price = tick_price.Floor(9.33);
  // EXPECT_NEAR(price, 9.3, 1e-10);

  price = tick_price.Round(9.33);
  EXPECT_NEAR(price, 9.3, 1e-10);

  std::string price_str;
  auto tick_price1 = FixedTickPrice(0.3);
  price_str = tick_price1.Format(9.33);
  EXPECT_EQ(price_str, "9.3");

  auto tick_price2 = FixedTickPrice(0.001);
  price_str = tick_price2.Format(9.33);
  EXPECT_EQ(price_str, "9.330");

  auto tick_price3 = FixedTickPrice(10);
  price_str = tick_price3.Format(9.33);
  EXPECT_EQ(price_str, "10");

  auto tick_price4 = FixedTickPrice(0.25);
  price_str = tick_price4.Format(9.249999999999);
  EXPECT_EQ(price_str, "9.25");

  auto tick_price5 = FixedTickPrice(0.25);
  price_str = tick_price5.Format(9);
  EXPECT_EQ(price_str, "9.00");
}

#if 0
TEST(RangeVariableTickPrice, range_variable_tick_price) {
  using namespace coin2::exchange::base::symbology;

  class SampleTickPrice : public RangeVariableTickPrice {
   public:
    int max_precision() const override {
      return 2.0;
    }

    double GetTick(double price_corrected) const override {
      CHECK(0 < price_corrected && price_corrected < 50000000)
        << "Price_corrected out of normal range: %s" << price_corrected;

      if (price_corrected >= 2000000.) {
        return 1000.;
      } else if (price_corrected >= 1000000.) {
        return 500.;
      } else if (price_corrected >= 500000.) {
        return 100.;
      } else if (price_corrected >= 100000.) {
        return 50.;
      } else if (price_corrected >= 10000.) {
        return 10.;
      } else if (price_corrected >= 1000.) {
        return 5.;
      } else if (price_corrected >= 100.) {
        return 1.;
      } else if (price_corrected >= 10.) {
        return 0.1;
      } else {
        return 0.01;
      }
    }
  };

  double price;
  double tick;
  double prev_price;
  double next_price;
  auto tick_price = SampleTickPrice();

  price = 2000000 + 1;
  tick = tick_price.GetTick(price);
  EXPECT_NEAR(tick, 1000, 1e-10);

  price = 12;
  prev_price = tick_price.GetPrevPrice(price);
  next_price = tick_price.GetNextPrice(price);
  EXPECT_NEAR(price - 0.1, prev_price, 1e-10);
  EXPECT_NEAR(price + 0.1, next_price, 1e-10);

  price = 1025;
  prev_price = tick_price.GetPrevPrice(price);
  next_price = tick_price.GetNextPrice(price);
  EXPECT_NEAR(price - 5, prev_price, 1e-10);
  EXPECT_NEAR(price + 5, next_price, 1e-10);

  price = 9990;
  next_price = tick_price.GetNextPrice(price);
  EXPECT_NEAR(price + 5, next_price, 1e-10);

  next_price = tick_price.GetNextPrice(next_price);
  EXPECT_NEAR(price + 10, next_price, 1e-10);

  next_price = tick_price.GetNextPrice(next_price);
  EXPECT_NEAR(price + 10 + 10, next_price, 1e-10);

  price = tick_price.Floor(12.101);
  EXPECT_NEAR(price, 12.1, 1e-10);

  price = tick_price.Ceil(12.181);
  EXPECT_NEAR(price, 12.2, 1e-10);

  price = tick_price.Round(12.181);
  EXPECT_NEAR(price, 12.2, 1e-10);

  price = tick_price.Floor(2001);
  EXPECT_NEAR(price, 2000, 1e-10);

  price = tick_price.Ceil(2001);
  EXPECT_NEAR(price, 2005, 1e-10);

  price = tick_price.Round(2002);
  EXPECT_NEAR(price, 2000, 1e-10);

  price = tick_price.Round(2003);
  EXPECT_NEAR(price, 2005, 1e-10);

  std::string price_str;
  price_str = tick_price.Format(2003);
  EXPECT_EQ(price_str, "2005");

  price_str = tick_price.Format(55.2121);
  EXPECT_EQ(price_str, "55.2");
}
#endif

TEST(FixedTickQty, fixed_tick_qty) {
  using namespace coin2::exchange::base::symbology;

  double qty;
  auto tick_qty = FixedTickQty(0.1);
  qty = 9.01;

  double ceil_qty = tick_qty.Ceil(qty);
  EXPECT_NEAR(ceil_qty, 9.1, 1.e-10);

  double floor_qty = tick_qty.Floor(qty);
  EXPECT_NEAR(floor_qty, 9.0, 1.e-10);

  double round_qty = tick_qty.Round(qty);
  EXPECT_NEAR(round_qty, 9.0, 1.e-10);

  std::string qty_str;
  qty_str = tick_qty.Format(9.221);
  EXPECT_EQ(qty_str, "9.2");

  qty_str = tick_qty.Format(3.09);
  EXPECT_EQ(qty_str, "3.1");

  auto tick_qty4 = FixedTickQty(0.0001);
  qty_str = tick_qty4.Format(331.2373);
  EXPECT_EQ(qty_str, "331.2373");
}
